/*
 * Copyright (c) 2023, LXMS (lxinet.cn).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.lxinet.lxms.framework.mybatis.provider;

import cn.lxinet.lxms.framework.core.vo.Page;
import cn.lxinet.lxms.framework.mybatis.annotation.Column;
import cn.lxinet.lxms.framework.mybatis.annotation.Id;
import cn.lxinet.lxms.framework.mybatis.annotation.Ignore;
import cn.lxinet.lxms.framework.mybatis.conditions.SqlWrapper;
import cn.lxinet.lxms.framework.mybatis.helper.TableHelper;
import io.micrometer.common.util.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.jdbc.SQL;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * sql封装
 *
 * @author zcx
 * @date 2023/07/24
 */
public class SqlProvider<T> {
    private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String FIND_BY_COLUMNS = "findByColumns";
    private static final Set<String> ignoreColumns = new HashSet<>();
    static {
        ignoreColumns.add("createTime");
        ignoreColumns.add("updateTime");
    }

    public String selectList(SqlWrapper<T> sqlWrapper) {
        SQL sql = new SQL();
        sql.SELECT("*").FROM(TableHelper.getTableName(sqlWrapper.getEntityClass()));
        String logicDelColumn = TableHelper.getLogicDel(sqlWrapper.getEntityClass());
        if (logicDelColumn != null){
            sql.WHERE(logicDelColumn + " = " + TableHelper.getLogicNoDelValue(sqlWrapper.getEntityClass()));
        }
        if (StringUtils.isNotBlank(sqlWrapper.getOrderBy())) {
            sql.ORDER_BY(sqlWrapper.getOrderBy());
        }
        return sql.toString();
    }

    public String selectPage(@Param("page") Page page, @Param("sqlWrapper") SqlWrapper<T> sqlWrapper) {
        SQL sql = new SQL();
        sql.SELECT("*").FROM(TableHelper.getTableName(sqlWrapper.getEntityClass()));
        String logicDelColumn = TableHelper.getLogicDel(sqlWrapper.getEntityClass());
        if (logicDelColumn != null){
            sql.WHERE(logicDelColumn + " = " + TableHelper.getLogicNoDelValue(sqlWrapper.getEntityClass()));
        }
        if (StringUtils.isNotBlank(sqlWrapper.getOrderBy())) {
            sql.ORDER_BY(sqlWrapper.getOrderBy());
        }
        return sql.toString();
    }

    public String selectById(@Param("id") Long id, @Param("c") Class<?> c) {
        SQL sql = new SQL();
        sql.SELECT("*").FROM(TableHelper.getTableName(c)).WHERE(TableHelper.getIdName(c) + " = #{" + TableHelper.getIdName(c) + "}");
        String logicDelColumn = TableHelper.getLogicDel(c);
        if (logicDelColumn != null){
            sql.WHERE(logicDelColumn + " = " + TableHelper.getLogicNoDelValue(c));
        }
        return sql.toString();
    }

    public String selectByIds(@Param("ids") List<Long> ids, @Param("c") Class<?> c) {
        StringBuilder sql = new StringBuilder();
        sql.append("select * from ").append(TableHelper.getTableName(c)).append(" where ").append(TableHelper.getIdName(c))
                .append(" in (").append(listToString(ids, ",")).append(")");
        String logicDelColumn = TableHelper.getLogicDel(c);
        if (logicDelColumn != null){
            sql.append(" and ").append(logicDelColumn).append(" = ").append(TableHelper.getLogicNoDelValue(c));
        }
        return sql.toString();
    }

    public String deleteById(@Param("id") Long id, @Param("c") Class<?> c) {
        SQL sql = new SQL();
        String logicDelColumn = TableHelper.getLogicDel(c);
        if (logicDelColumn == null){
            sql.DELETE_FROM(TableHelper.getTableName(c)).WHERE(TableHelper.getIdName(c) + " = #{" + TableHelper.getIdName(c) + "}");
        }else {
            sql.UPDATE(TableHelper.getTableName(c)).SET(logicDelColumn + " = " + TableHelper.getLogicDelValue(c))
                    .WHERE(TableHelper.getIdName(c) + " = #{" + TableHelper.getIdName(c) + "}");
        }
        return sql.toString();
    }

    public String delete(T t) {
        SQL sql = new SQL();
        String logicDelColumn = TableHelper.getLogicDel(t.getClass());
        if (logicDelColumn == null){
            sql.DELETE_FROM(TableHelper.getTableName(t.getClass())).WHERE(TableHelper.getIdName(t.getClass()) + " = #{" + TableHelper.getIdName(t.getClass()) + "}");
        }else {
            sql.UPDATE(TableHelper.getTableName(t.getClass())).SET(logicDelColumn + " = " + TableHelper.getLogicDelValue(t.getClass()))
                    .WHERE(TableHelper.getIdName(t.getClass()) + " = #{" + TableHelper.getIdName(t.getClass()) + "}");
        }
        return sql.toString();
    }


    public String insert(T t) {
        SQL sql = new SQL();
        sql.INSERT_INTO(TableHelper.getTableName(t.getClass()));
        Field[] field = t.getClass().getDeclaredFields();
        for (Field nameField : field) {
            Ignore ignore = nameField.getAnnotation(Ignore.class);
            if (ignore != null){
                continue;
            }
            Column column = nameField.getAnnotation(Column.class);
            Id id = nameField.getAnnotation(Id.class);
            String columnName = nameField.getName();
            Object val = getFieldValue(t, columnName);
            if (column != null){
                columnName = column.value();
            }
            if (ignoreColumns.contains(columnName)){
                continue;
            }
            if (null != val){
                if (null == id) {
                    if (column == null){
                        sql.INTO_COLUMNS(humpToUnderline(columnName));
                    }else {
                        sql.INTO_COLUMNS(column.value());
                    }
                    sql.INTO_VALUES("#{" + columnName + "}");
                }
            }
        }
        return sql.toString();
    }

    public String batchInsert(Collection<T> list, @Param("c") Class<?> c) {
        StringBuilder sql = new StringBuilder();
        sql.append("insert into ").append(TableHelper.getTableName(c)).append(" (");
        Field[] field = c.getDeclaredFields();
        for (Field nameField : field) {
            Ignore ignore = nameField.getAnnotation(Ignore.class);
            if (ignore != null){
                continue;
            }
            Column column = nameField.getAnnotation(Column.class);
            Id id = nameField.getAnnotation(Id.class);
            String columnName = nameField.getName();
            if (column != null){
                columnName = column.value();
            }
            if (ignoreColumns.contains(columnName)){
                continue;
            }
            if (id == null){
                sql.append(humpToUnderline(columnName)).append(",");
            }
        }
        sql.deleteCharAt(sql.length()-1);
        sql.append(") values");

        StringBuilder mfPattern = new StringBuilder("(");
        for (Field nameField : field) {
            Ignore ignore = nameField.getAnnotation(Ignore.class);
            if (ignore != null){
                continue;
            }
            Column column = nameField.getAnnotation(Column.class);
            Id id = nameField.getAnnotation(Id.class);
            String columnName = nameField.getName();
            if (column != null){
                columnName = column.value();
            }
            if (ignoreColumns.contains(columnName)){
                continue;
            }
            if (id == null){
                mfPattern.append("#'{'list[{0}]." + columnName + "},");
            }
        }
        mfPattern.deleteCharAt(mfPattern.length()-1);
        mfPattern.append(")");
        MessageFormat mf = new MessageFormat(mfPattern.toString());
        for (int i = 0; i < list.size(); i++) {
            sql.append(mf.format(new Object[]{i}));
            if (i < list.size() - 1)
                sql.append(",");
        }
        return sql.toString();
    }

    public String updateById(T t) {
        SQL sql = new SQL();
        sql.UPDATE(TableHelper.getTableName(t.getClass()));
        Field[] fields = t.getClass().getDeclaredFields();
        for (Field field : fields) {
            Ignore ignore = field.getAnnotation(Ignore.class);
            if (null != ignore) {
                continue;
            }
            Column column = field.getAnnotation(Column.class);
            Object val = getFieldValue(t, field.getName());
            if (null != val){
                String columnName = column == null ? humpToUnderline(field.getName()) : column.value();
                sql.SET(columnName + " = #{" + field.getName() + "}");
            }
        }
        sql.WHERE(TableHelper.getIdName(t.getClass()) + " = #{" + TableHelper.getIdName(t.getClass()) + "}");
        return sql.toString();
    }


    public String selectByColumns(Map<String, Object> params, Class<?> c) {
        SQL sql = new SQL();
        Iterator<Map.Entry<String, Object>> iterator = params.entrySet().iterator();
        int i = 0;
        sql.SELECT("*").FROM(TableHelper.getTableName(c));
        while (iterator.hasNext()) {
            String column = iterator.next().getKey();
            if (!column.startsWith("param")) {
                sql.WHERE(column + " = #{" + column + "}");
            }
            i++;
        }
        return sql.toString();
    }


    /**
     * 获取字段值
     *
     * @param t     t
     * @param field 场
     * @return {@link Object}
     */
    private Object getFieldValue(T t, String field) {
        String firstLetter = field.substring(0,1).toUpperCase();
        String getMethodName = "get" + firstLetter + field.substring(1);
        //获取方法对象
        Method getMethod = null;
        try {
            getMethod = t.getClass().getMethod(getMethodName, new Class[]{});
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        Object value = null;
        try {
            value = getMethod.invoke(t, new Object[]{});
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return value;
    }


    /**
     * 驼峰转下划线
     *
     * @param str str
     * @return {@link String}
     */
    public static String humpToUnderline(String str) {
        Pattern compile = Pattern.compile("[A-Z]");
        Matcher matcher = compile.matcher(str);
        StringBuffer sb = new StringBuffer();
        while(matcher.find()) {
            matcher.appendReplacement(sb,  "_" + matcher.group(0).toLowerCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    /**
     * 集合转换为字符串
     *
     * @param list
     * @param separator
     * @return
     */
    public String listToString(List list, String separator) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            sb.append(list.get(i)).append(separator);
        }
        return sb.toString().substring(0, sb.toString().length() - 1);
    }

}
